﻿// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Roslynator.CodeActions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Roslynator.CSharp.CSharpFactory;

namespace Roslynator.CSharp;

/// <summary>
/// A set of extension methods for syntax. These methods are dependent on the workspace layer.
/// </summary>
public static class WorkspaceSyntaxExtensions
{
    private static readonly SyntaxAnnotation[] _formatterAnnotationArray = [Formatter.Annotation];

    private static readonly SyntaxAnnotation[] _simplifierAnnotationArray = [Simplifier.Annotation];

    private static readonly SyntaxAnnotation[] _renameAnnotationArray = [RenameAnnotation.Create()];

    private static readonly SyntaxAnnotation[] _navigationAnnotationArray = [NavigationAnnotation.Annotation];

    private static readonly SyntaxAnnotation[] _formatterAndSimplifierAnnotationArray = [Formatter.Annotation, Simplifier.Annotation];

    #region ExpressionSyntax
    /// <summary>
    /// Creates parenthesized expression that is parenthesizing the specified expression.
    /// </summary>
    /// <param name="includeElasticTrivia">If true, add elastic trivia.</param>
    /// <param name="simplifiable">If true, attach <see cref="Simplifier.Annotation"/> to the parenthesized expression.</param>
    public static ParenthesizedExpressionSyntax Parenthesize(
        this ExpressionSyntax expression,
        bool includeElasticTrivia = true,
        bool simplifiable = true)
    {
        ParenthesizedExpressionSyntax parenthesizedExpression;

        if (includeElasticTrivia)
        {
            parenthesizedExpression = ParenthesizedExpression(expression.WithoutTrivia());
        }
        else
        {
            parenthesizedExpression = ParenthesizedExpression(
                Token(SyntaxTriviaList.Empty, SyntaxKind.OpenParenToken, SyntaxTriviaList.Empty),
                expression.WithoutTrivia(),
                Token(SyntaxTriviaList.Empty, SyntaxKind.CloseParenToken, SyntaxTriviaList.Empty));
        }

        return parenthesizedExpression
            .WithTriviaFrom(expression)
            .WithSimplifierAnnotationIf(simplifiable);
    }

    internal static ExpressionSyntax ParenthesizeIf(
        this ExpressionSyntax expression,
        bool condition,
        bool includeElasticTrivia = true,
        bool simplifiable = true)
    {
        return (condition) ? Parenthesize(expression, includeElasticTrivia, simplifiable) : expression;
    }
    #endregion ExpressionSyntax

    #region SimpleNameSyntax
    internal static MemberAccessExpressionSyntax QualifyWithThis(this SimpleNameSyntax simpleName, bool simplifiable = true)
    {
        return SimpleMemberAccessExpression(ThisExpression(), simpleName).WithSimplifierAnnotationIf(simplifiable);
    }
    #endregion SimpleNameSyntax

    #region SyntaxNode
    /// <summary>
    /// Creates a new node with the <see cref="Formatter.Annotation"/> attached.
    /// </summary>
    public static TNode WithFormatterAnnotation<TNode>(this TNode node) where TNode : SyntaxNode
    {
        if (node is null)
            throw new ArgumentNullException(nameof(node));

        return node.WithAdditionalAnnotations(_formatterAnnotationArray);
    }

    /// <summary>
    /// Creates a new node with the <see cref="Simplifier.Annotation"/> attached.
    /// </summary>
    public static TNode WithSimplifierAnnotation<TNode>(this TNode node) where TNode : SyntaxNode
    {
        if (node is null)
            throw new ArgumentNullException(nameof(node));

        return node.WithAdditionalAnnotations(_simplifierAnnotationArray);
    }

    internal static TNode WithNavigationAnnotation<TNode>(this TNode node) where TNode : SyntaxNode
    {
        if (node is null)
            throw new ArgumentNullException(nameof(node));

        SyntaxToken token = node.GetFirstToken();

        if (token.IsKind(SyntaxKind.None))
            return node;

        return node.ReplaceToken(token, token.WithNavigationAnnotation());
    }

    internal static TNode WithSimplifierAnnotationIf<TNode>(this TNode node, bool condition) where TNode : SyntaxNode
    {
        return (condition) ? node.WithAdditionalAnnotations(_simplifierAnnotationArray) : node;
    }

    internal static TNode WithFormatterAndSimplifierAnnotation<TNode>(this TNode node) where TNode : SyntaxNode
    {
        if (node is null)
            throw new ArgumentNullException(nameof(node));

        return node.WithAdditionalAnnotations(_formatterAndSimplifierAnnotationArray);
    }
    #endregion SyntaxNode

    #region SyntaxToken
    /// <summary>
    /// Adds <see cref="Formatter.Annotation"/> to the specified token, creating a new token of the same type with the <see cref="Formatter.Annotation"/> on it.
    /// </summary>
    public static SyntaxToken WithFormatterAnnotation(this SyntaxToken token)
    {
        return token.WithAdditionalAnnotations(_formatterAnnotationArray);
    }

    /// <summary>
    /// Adds <see cref="Simplifier.Annotation"/> to the specified token, creating a new token of the same type with the <see cref="Simplifier.Annotation"/> on it.
    /// "Rename" annotation is specified by <see cref="RenameAnnotation.Kind"/>.
    /// </summary>
    public static SyntaxToken WithSimplifierAnnotation(this SyntaxToken token)
    {
        return token.WithAdditionalAnnotations(_simplifierAnnotationArray);
    }

    /// <summary>
    /// Adds navigation annotation to the specified token, creating a new token of the same type with the navigation annotation on it.
    /// Navigation annotation allows to mark a token that should be selected after the code action is applied.
    /// </summary>
    public static SyntaxToken WithNavigationAnnotation(this SyntaxToken token)
    {
        return token.WithAdditionalAnnotations(_navigationAnnotationArray);
    }

    /// <summary>
    /// Adds "rename" annotation to the specified token, creating a new token of the same type with the "rename" annotation on it.
    /// "Rename" annotation is specified by <see cref="RenameAnnotation.Kind"/>.
    /// </summary>
    public static SyntaxToken WithRenameAnnotation(this SyntaxToken token)
    {
        return token.WithAdditionalAnnotations(_renameAnnotationArray);
    }
    #endregion SyntaxToken
}
